//
//  NvTimelineUtils.m
//  SDKDemo
//
//  Created by shizhouhu on 2018/6/26.
//  Copyright © 2018年 meishe. All rights reserved.
//

#import "NvTimelineUtils.h"
#import "NvTimelineData.h"
#import "NvsVideoClip.h"
#import "NvsAudioClip.h"
#import "NvsTimelineCaption.h"
#import "NvsTimelineAnimatedSticker.h"
#import "NvsParticleSystemContext.h"
#import "NvsTimelineVideoFx.h"
#import "NvsStreamingContext.h"
#import "NvUtils.h"
#import "NvsVideoFx.h"
#import "NVHeader.h"
#import "NvSDKUtils.h"

@implementation NvTimelineUtils

static NvTimelineUtils *sharedInstance = nil;

+ (NvTimelineUtils *)sharedInstance {
    if (nil != sharedInstance) {
        return sharedInstance;
    }
    
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        sharedInstance = [[NvTimelineUtils alloc] init];
    });
    
    return sharedInstance;
}


+ (NvsTimeline *)createTimeline:(NvEditMode)editMode {
    NvsStreamingContext *context = [NvsStreamingContext sharedInstance];
    NvsSize size = [NvTimelineUtils calculateTimelineSize:editMode];
    NvsVideoResolution videoEditRes;
    videoEditRes.imageWidth = size.width;
    videoEditRes.imageHeight = size.height;
    videoEditRes.imagePAR = (NvsRational){1, 1};
    NvsRational videoFps = {25, 1};
    NvsAudioResolution audioEditRes;
    audioEditRes.sampleRate = 48000;
    audioEditRes.channelCount = 2;
    audioEditRes.sampleFormat = NvsAudSmpFmt_S16;
    NvsTimeline *timeline = [context createTimeline:&videoEditRes videoFps:&videoFps audioEditRes:&audioEditRes];
    [timeline appendVideoTrack];
    [timeline appendAudioTrack]; //音乐轨道
    [timeline appendAudioTrack]; //配音轨道
    return timeline;
}

+ (void)removeTimeline:(NvsTimeline *)timeline {
    NvsStreamingContext *context = [NvsStreamingContext sharedInstance];
    [context removeTimeline:timeline];
}

+ (NvsSize)calculateTimelineSize:(NvEditMode)editMode {
    int compileRes = [NvUtils compileResolutionSetting];
    NvsSize size;
    if (editMode == NvEditMode16v9) {
        size.height = compileRes;
        size.width = compileRes * 16 / 9;
    } else if (editMode == NvEditMode1v1) {
        size.height = compileRes;
        size.width = compileRes;
    } else if (editMode == NvEditMode9v16) {
        size.width = compileRes;
        size.height = compileRes * 16 / 9;
    } else if (editMode == NvEditMode3v4) {
        size.width = compileRes;
        size.height = compileRes * 4 / 3;
    } else if (editMode == NvEditMode4v3) {
        size.width = compileRes * 4 / 3;
        size.height = compileRes;
    } else {
        size.width = 1280;
        size.height = 720;
    }
    return size;
}

+ (void)recreateTimeline:(NvsTimeline *)timeline
{
    if (timeline == nil ) {
        return;
    }
    // 先移除主题，确保添加前时间线是干净的
    [timeline removeCurrentTheme];
    [timeline deleteWatermark];
    
    NvTimelineData *timelineData = [NvTimelineData sharedInstance];
    [NvTimelineUtils resetEditData:timeline editDataArray:timelineData.editDataArray];
    
    NSMutableArray *order = [timelineData dataOrder];
    for (NSString *name in order) {
        if ([name isEqualToString:@"Music"]) {
            [NvTimelineUtils resetMusicTrack:timeline musicDataArray:timelineData.musicDataArray];
        }
        if ([name isEqualToString:@"Dubbing"]) {
            [NvTimelineUtils resetDubbingTrack:timeline dubbingModel:timelineData.dubbingModel];
        }
        if ([name isEqualToString:@"Filter"]) {
            [NvTimelineUtils resetVideoFx:timeline videoFxDataArray:timelineData.videoFxDataArray];
        }
        if ([name isEqualToString:@"Sticker"]) {
            [NvTimelineUtils resetSticker:timeline stickerDataArray:timelineData.stickerDataArray];
        }
        if ([name isEqualToString:@"Caption"]) {
            [NvTimelineUtils resetCaption:timeline captionDataArray:timelineData.captionDataArray];
        }
        if ([name isEqualToString:@"Particle"]) {
            [NvTimelineUtils resetParticle:timeline particleDataArray:timelineData.particleDataArray];
        }
        if ([name isEqualToString:@"Transition"]) {
            [NvTimelineUtils resetTransition:timeline transitionDataArray:timelineData.transitionDataArray];
        }
        if ([name isEqualToString:@"Watermark"]) {
            [NvTimelineUtils resetWatermark:timeline watermarkInfo:timelineData.watermarkInfo];
        }
        if ([name isEqualToString:@"Theme"]) {
            [NvTimelineUtils resetTheme:timeline themeInfo:timelineData.themeInfo];
            [NvTimelineUtils resetCaption:timeline captionDataArray:timelineData.captionDataArray];
        }
    }
}

+ (void)resetCaption:(NvsTimeline *)timeline captionDataArray:(NSArray *)captionDataArray {
    NvsTimelineCaption *nextCaption = [timeline getFirstCaption];
    do {
        NSLog(@"-------%d,%d,%@",nextCaption.category,nextCaption.roleInTheme,nextCaption.getText);
        if (nextCaption.category == NvsThemeCategory && nextCaption.roleInTheme != NvsRoleInThemeGeneral) {
            nextCaption = [timeline getNextCaption:nextCaption];
        }else{
            nextCaption = [timeline removeCaption:nextCaption];
        }
    } while (nextCaption);
    // 根据编辑数据重新添加字幕
    for (int i = 0; i < captionDataArray.count; i++) {
        NvCaptionInfoModel * info = (NvCaptionInfoModel *)captionDataArray[i];
        if (info.roleInTheme != NvsRoleInThemeGeneral && info.category == NvsThemeCategory) {
            continue;
        }
        NvsTimelineCaption* caption = [timeline addCaption:info.text inPoint:info.inPoint duration:info.outPoint - info.inPoint captionStylePackageId:info.styleId];
        [caption setAttachment:info forKey:@"captionInfoModel"];
        if(caption == nil){
            continue;
        }
        
        CGFloat r,g,b,a;
        if (info.color) {
            [info.color getRed:&r green:&g blue:&b alpha:&a];
            NvsColor color = {r,g,b,a};
            [caption setTextColor:&color];
        }
        
//        [caption setFontSize:info.fontSize];
        [caption setTextAlignment:info.alignment];
        
        [caption setCaptionTranslation:info.translation];
        
        [caption setScaleX:info.scale];
        [caption setScaleY:info.scale];
        [caption setRotationZ:info.rotation];
        if (info.isDrawOutline) {
            [caption setDrawOutline:YES];
            CGFloat r,g,b,a;
            [info.outlineColor getRed:&r green:&g blue:&b alpha:&a];
            NvsColor color = {r,g,b,a};
            [caption setOutlineColor:&color];
            [caption setOutlineWidth:info.outlineWidth];
        } else {
            [caption setDrawOutline:NO];
        }
        if (info.isBold) {
            [caption setBold:YES];
        } else {
           [caption setBold:NO];
        }
        if (info.isItalic) {
            [caption setItalic:YES];
        } else {
            [caption setItalic:NO];
        }
        if (info.isDrawShadow) {
            [caption setDrawShadow:YES];
            NvsColor color = {0,0,0,0.5};
            [caption setShadowColor:&color];
            [caption setShadowOffset:CGPointMake(10, -10)];
        } else {
            [caption setDrawShadow:NO];
        }
        if (info.fontFilePath && ![info.fontFilePath isEqualToString:@""]) {
            [caption setFontWithFilePath:info.fontFilePath];
        } else {
            [caption setFontWithFilePath:@""];
        }
        
    }
}

+ (void)resetSticker:(NvsTimeline *)timeline stickerDataArray:(NSArray *)stickerDataArray{
    NvsTimelineAnimatedSticker *sticker = [timeline getFirstAnimatedSticker];
    while (sticker) {
        sticker = [timeline removeAnimatedSticker:sticker];
    }
    // 根据编辑数据重新添加贴纸
    for (int i = 0; i < stickerDataArray.count; i++) {
        NvStickerInfoModel *info = (NvStickerInfoModel *)stickerDataArray[i];
        NvsTimelineAnimatedSticker * sticker = nil;
        if (info.isCustomSticer) {
            sticker = [timeline addCustomAnimatedSticker:info.inPoint duration:info.outPoint - info.inPoint animatedStickerPackageId:info.packageId customImagePath:info.customImagePath];
        } else {
            sticker = [timeline addAnimatedSticker:info.inPoint duration:info.outPoint - info.inPoint animatedStickerPackageId:info.packageId];
        }
        if(sticker == nil){
            continue;
        }
        
        [sticker setAttachment:info forKey:@"stickerInfoModel"];
        
        [sticker setTranslation:info.translation];
        [sticker setScale:info.scale];
        [sticker setRotationZ:info.rotation];
        [sticker setVolumeGain:info.volume rightVolumeGain:info.volume];
    }
}

+ (void)resetTheme:(NvsTimeline *)timeline themeInfo:(NvThemeInfoModel *)themeInfo{
    [timeline removeCurrentTheme];
    [timeline applyTheme:themeInfo.themeName];
    NSMutableArray *musicDataArray = [[NvTimelineData sharedInstance] musicDataArray];
    if ([musicDataArray count] > 0) {
        [timeline setThemeMusicVolumeGain:0 rightVolumeGain:0];
    } else {
        [timeline setThemeMusicVolumeGain:themeInfo.volume rightVolumeGain:themeInfo.volume];
    }
    if (themeInfo.thenmeRoleInTheme == NvsRoleInThemeTitle) {
        [timeline setThemeTitleCaptionText:themeInfo.themeString];
    }else if(themeInfo.thenmeRoleInTheme == NvsRoleInThemeTrailer){
        [timeline setThemeTrailerCaptionText:themeInfo.themeString];
    }
    //应用两次是因为改完主题的字幕文字的时候，不生效
    [timeline applyTheme:themeInfo.themeName];
}

+ (void)resetWatermark:(NvsTimeline *)timeline watermarkInfo:(NvWatermarkInfoModel *)watermarkInfo{
    [timeline deleteWatermark];
    if (watermarkInfo.isCaf) {
        if ([NvTimelineUtils sharedInstance].isVideoFx) {
            NvsTimelineVideoFx *videoFx = [timeline addBuiltinTimelineVideoFx:0 duration:timeline.duration videoFxName:@"Storyboard"];
            NSString *descString = [NSString stringWithFormat:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><storyboard sceneWidth=\"Swidth\" sceneHeight=\"SHeight\"><track source=\"cafFile\" width=\"Nwidth\" height=\"Nheight\" clipStart=\"0\" clipDuration=\"2000\" repeat=\"true\"><effect name=\"transform\"><param name=\"opacity\" value=\"1\"/><param name=\"transX\" value=\"0\"/><param name=\"transY\" value=\"0\"/></effect></track></storyboard>"];
            
            descString = [descString stringByReplacingOccurrencesOfString:@"Swidth" withString:[NSString stringWithFormat:@"%d",(int)watermarkInfo.sceneWidth]];
            descString = [descString stringByReplacingOccurrencesOfString:@"SHeight" withString:[NSString stringWithFormat:@"%d",(int)watermarkInfo.sceneHeight]];
            descString = [descString stringByReplacingOccurrencesOfString:@"Nwidth" withString:[NSString stringWithFormat:@"%d",(int)watermarkInfo.displayWidth]];
            descString = [descString stringByReplacingOccurrencesOfString:@"Nheight" withString:[NSString stringWithFormat:@"%d",(int)watermarkInfo.displayHeight]];
            descString = [descString stringByReplacingOccurrencesOfString:@"cafFile" withString:[NSString stringWithFormat:@"%@.caf",watermarkInfo.imageUrl]];
            NSLog(@"descString==%@",descString);
            [videoFx setStringVal:@"Resource Dir" val:WATEMARK_PATH];
            [videoFx setStringVal:@"Description String" val:descString];
            [videoFx setBooleanVal:@"Is Animated Sticker" val:true];
            [videoFx setFloatVal:@"Sticker TransX" val:watermarkInfo.marginX];
            [videoFx setFloatVal:@"Sticker TransY" val:watermarkInfo.marginY];
        }
    }else{
        NSString *path = [WATEMARK_PATH stringByAppendingPathComponent:[watermarkInfo.imageUrl stringByAppendingString:@".png"]];
        [timeline addWatermark:path displayWidth:watermarkInfo.displayWidth displayHeight:watermarkInfo.displayHeight opacity:watermarkInfo.opacity position:watermarkInfo.position marginX:watermarkInfo.marginX marginY:watermarkInfo.marginY];
    }
}

+ (void)resetParticle:(NvsTimeline *)timeline particleDataArray:(NSArray *)particleDataArray {
    NvsTimelineVideoFx *videoFx = [timeline getFirstTimelineVideoFx];
    while (videoFx) {
        NvsParticleSystemContext *particleContext = [videoFx getParticleSystemContext];
        if (particleContext) {
            videoFx = [timeline removeTimelineVideoFx:videoFx];
            continue;
        }
        videoFx = [timeline getNextTimelineVideoFx:videoFx];
    }
    // 根据编辑数据重新添加粒子
    for (int i = 0; i < particleDataArray.count; i++) {
        NvParticleInfoModel *info = (NvParticleInfoModel *)particleDataArray[i];
        
        NvsTimelineVideoFx *particleVideoFx = [timeline addPackagedTimelineVideoFx:info.inPoint duration:info.outPoint - info.inPoint videoFxPackageId:info.name];
        if (particleVideoFx == nil) {
            continue;
        }
        NvsParticleSystemContext *particleSystemContext = [particleVideoFx getParticleSystemContext];
        if (particleSystemContext == nil) {
            continue;
        }
        
        for (NSMutableDictionary * dict in info.particleLocation) {
            for (NSString *key in dict) {
                CGPoint point = [(NSValue *)dict[key] CGPointValue];
                for (int j = 0; j < info.emitterName.count; j++) {
                    [particleSystemContext appendPositionToEmitterPositionCurve:info.emitterName[j]
                                                                      curveTime:(float)([(NSString *)key longLongValue] - info.inPoint)/NV_TIME_BASE
                                                               emitterPositionX:point.x
                                                               emitterPositionY:point.y];
                    [particleSystemContext setEmitterRateGain:info.name emitterGain:info.particleRateValue];
                    [particleSystemContext SetEmitterParticleSizeGain:info.name emitterGain:info.particleSizeValue];
                }
            }
        }
    }
}

+ (void)resetEditData:(NvsTimeline *)timeline editDataArray:(NSArray *)editDataArray {
    NvsVideoTrack *videoTrack = [timeline getVideoTrackByIndex:0];
    
    if (videoTrack == nil) {
        return;
    }
    [videoTrack removeAllClips];
    
    for (int i = 0; i < editDataArray.count; i++) {
        NvEditDataModel *editDataModel = editDataArray[i];
        NvsVideoClip *videoClip = [videoTrack appendClip:editDataModel.isImage ? editDataModel.localIdentifier : editDataModel.videoPath
                                                  trimIn:editDataModel.trimIn
                                                 trimOut:editDataModel.trimOut];
        BOOL isBlur = [[[NSUserDefaults standardUserDefaults] valueForKey:@"NvBackgroudBlurFilled"] boolValue];
        if (isBlur) {
            [videoClip setSourceBackgroundMode:NvsSourceBackgroundModeBlur];
        } else {
            [videoClip setSourceBackgroundMode:NvsSourceBackgroundModeColorSolid];
        }
        if (!videoClip) {
            continue;
        }
        [videoClip changeSpeed:editDataModel.speed keepAudioPitch:YES];
        
        [videoClip setVolumeGain:editDataModel.volume rightVolumeGain:editDataModel.volume];
        
        NvsVideoFx *transVideoFx = [videoClip appendBuiltinFx:@"Transform 2D"];
        [transVideoFx setFloatVal:@"Scale X" val:editDataModel.scaleX];
        [transVideoFx setFloatVal:@"Scale Y" val:editDataModel.scaleY];
        [videoClip setPan:editDataModel.pan andScan:editDataModel.scan];
//        [transVideoFx setFloatVal:@"Trans X" val:editDataModel.translation.x];
//        [transVideoFx setFloatVal:@"Trans Y" val:editDataModel.translation.y];
        NvsVideoFx *colorVideoFx = [videoClip appendBuiltinFx:@"Color Property"];
        [colorVideoFx setFloatVal:@"Brightness" val:editDataModel.brightness];
        [colorVideoFx setFloatVal:@"Saturation" val:editDataModel.saturation];
        [colorVideoFx setFloatVal:@"Contrast" val:editDataModel.contrast];
        NvsVideoFx *sharpenVideoFx = [videoClip appendBuiltinFx:@"Sharpen"];
        [sharpenVideoFx setFloatVal:@"Amount" val:editDataModel.Sharpen];
        NvsVideoFx *vignetteVideoFx = [videoClip appendBuiltinFx:@"Vignette"];
        [vignetteVideoFx setFloatVal:@"Degree" val:editDataModel.Vignette];
        
        if (editDataModel.rotation >= 0) {
            [videoClip setExtraVideoRotation:editDataModel.rotation];
        }
        if (editDataModel.isImage && editDataModel.isDefault) {
            [videoClip setImageMotionAnimationEnabled:editDataModel.hasMotion];
            [videoClip setImageMotionMode:editDataModel.motionMode];
            if (editDataModel.isArea) {
                NvsRect startRect = editDataModel.startRect;
                NvsRect endRect = editDataModel.endRect;
                [videoClip setImageMotionROI:&startRect endROI:&endRect];
            }

        }
    }
}

+ (NvsVideoRotation)getRotation:(NvsAVFileInfo *) fileInfo {
    if (fileInfo == nil) {
        return NvsVideoRotation_0;
    }
    NvsVideoRotation rotation = [fileInfo getVideoStreamRotation:0];
    if (rotation == NvsVideoRotation_90) {
        rotation = NvsVideoRotation_270;
    } else if (rotation == NvsVideoRotation_180) {
        rotation = NvsVideoRotation_180;
    } else if (rotation == NvsVideoRotation_270) {
        rotation = NvsVideoRotation_90;
    }
    return rotation;
}

+ (void)resetMusicTrack:(NvsTimeline *)timeline musicDataArray:(NSArray<NvMusicInfoModel *> *)musicDataArray {
    NvsAudioTrack *musicTrack = [timeline getAudioTrackByIndex:NV_MUSIC_SOUND_TRACK];
    [musicTrack removeAllClips];

    if ([musicDataArray count] > 0) {
        // 单段音乐，当裁剪长度小于视频长度时，循环
        // 多段音乐，当每一段的裁剪长度小于时间线入出点之差时，循环
        if ([musicDataArray count] == 1 && [(NvMusicInfoModel *)musicDataArray[0] isBGM]) {
            NvMusicInfoModel *musicInfo = musicDataArray[0];
            NvsAudioClip *clip = [musicTrack appendClip:musicInfo.musicPath
                                                 trimIn:musicInfo.trimIn
                                                trimOut:musicInfo.trimOut];
            [clip setVolumeGain:musicInfo.volume rightVolumeGain:musicInfo.volume];
            
            while (musicTrack.duration < [timeline duration]) {
                NvsAudioClip *audioClip = [musicTrack appendClip:musicInfo.musicPath
                                                          trimIn:musicInfo.trimIn
                                                         trimOut:musicInfo.trimOut];
                [audioClip setVolumeGain:musicInfo.volume rightVolumeGain:musicInfo.volume];
            }
        } else {
            for (int i = 0; i < musicDataArray.count; i++) {
                NvMusicInfoModel *musicInfo = musicDataArray[i];
                for (int j = 0; j < (musicInfo.outPoint - musicInfo.inPoint)/(float)(musicInfo.trimOut - musicInfo.trimIn); j++) {
                    int64_t trimOut = (j == (musicInfo.outPoint - musicInfo.inPoint)/(musicInfo.trimOut - musicInfo.trimIn))
                    ? musicInfo.trimIn + musicInfo.outPoint - musicInfo.inPoint - j*(musicInfo.trimOut-musicInfo.trimIn)
                    : musicInfo.trimOut;
                    NvsAudioClip *clip = [musicTrack addClip:musicInfo.musicPath
                                                     inPoint:musicInfo.inPoint + j*(musicInfo.trimOut-musicInfo.trimIn)
                                                      trimIn:musicInfo.trimIn
                                                     trimOut:trimOut];
                    if (musicInfo.isFade) {
                        if ((musicInfo.outPoint - musicInfo.inPoint)/(float)(musicInfo.trimOut - musicInfo.trimIn) == 1) {
                            clip.fadeInDuration = NV_TIME_BASE;
                            clip.fadeOutDuration = NV_TIME_BASE;
                        }else{
                            if (j == 0) {
                                clip.fadeInDuration = NV_TIME_BASE;
                                clip.fadeOutDuration = 0;
                            }else if ((musicInfo.outPoint - musicInfo.inPoint)/(float)(musicInfo.trimOut - musicInfo.trimIn) - j < 1){
                                clip.fadeInDuration = 0;
                                clip.fadeOutDuration = NV_TIME_BASE;
                            }
                        }
                    }
                    [clip setVolumeGain:musicInfo.volume rightVolumeGain:musicInfo.volume];
                }
            }
        }
        [timeline setThemeMusicVolumeGain:0 rightVolumeGain:0];
    } else {
        if (![NvUtils isStringEmpty:[timeline getCurrentThemeId]]) {
            NvThemeInfoModel *themeInfo = [[NvTimelineData sharedInstance] themeInfo];
            [timeline setThemeMusicVolumeGain:themeInfo.volume rightVolumeGain:themeInfo.volume];
        }
    }
   
}

+ (void)resetDubbingTrack:(NvsTimeline *)timeline dubbingModel:(NvDubbingModel *)dubbingModel {
    NvsAudioTrack *audioTrack = [timeline getAudioTrackByIndex:NV_DUBBING_SOUND_TRACK];
    if (audioTrack == nil) {
        audioTrack = [timeline appendAudioTrack];
    }
    [audioTrack removeAllClips];
    for (int i = 0; i < dubbingModel.dubbingInfoModels.count; i++) {
        NvDubbingInfoModel *dubbingDataModel = dubbingModel.dubbingInfoModels[i];
        NvsAudioClip *audioClip = [audioTrack addClip:dubbingDataModel.dubbingFilePath
                                              inPoint:dubbingDataModel.inPoint trimIn:dubbingDataModel.trimIn trimOut:dubbingDataModel.duration];
        [audioClip appendFx:dubbingDataModel.builtInFxName];
        [audioClip changeSpeed:dubbingDataModel.speed keepAudioPitch:YES];
        [audioClip setVolumeGain:dubbingDataModel.volume rightVolumeGain:dubbingDataModel.volume];
    }
    if (dubbingModel) {
        [audioTrack setVolumeGain:dubbingModel.volume rightVolumeGain:dubbingModel.volume];
    } else {
        [audioTrack setVolumeGain:1 rightVolumeGain:1];
    }
}

+ (void)resetTransition:(NvsTimeline *)timeline transitionDataArray:(NSArray *)transitionDataArray {
    NvsVideoTrack *videoTrack = [timeline getVideoTrackByIndex:0];
    [NvTimelineUtils removeAllVideoTransitions:videoTrack];
    
    for (int i = 0; i < transitionDataArray.count; i++) {
        NvTransitionInfoModel *info = transitionDataArray[i];
        if ([NvUtils isStringEmpty:info.builtinName]) {
            [videoTrack setPackagedTransition:i withPackageId:info.packageId];
        } else {
            [videoTrack setBuiltinTransition:i withName:info.builtinName];
        }
    }
}

+ (void)resetVideoFx:(NvsTimeline *)timeline videoFxDataArray:(NSArray *)videoFxDataArray {
    NvsTimelineVideoFx *videoFx = [timeline getFirstTimelineVideoFx];
    while (videoFx) {
        NvsParticleSystemContext *particleContext = [videoFx getParticleSystemContext];
        if (particleContext) {
            videoFx = [timeline getNextTimelineVideoFx:videoFx];
            continue;
        }
        videoFx = [timeline removeTimelineVideoFx:videoFx];
    }
    // 根据编辑数据重新添加滤镜
    for (int i = 0; i < videoFxDataArray.count; i++) {
        NvTimeFilterInfoModel *info = (NvTimeFilterInfoModel *)videoFxDataArray[i];
        int64_t inPoint = info.inPoint;
        int64_t duration = info.outPoint - info.inPoint;
        if (info.addInReverseMode) {
            inPoint = [timeline duration] - info.outPoint;
        }
        if ([NvSDKUtils isBuiltinFilter:info.name]) {
            NvsTimelineVideoFx *videoFx = [timeline addBuiltinTimelineVideoFx:inPoint duration:duration videoFxName:info.name];
            [videoFx setFilterIntensity:info.strength];
            if ([info.name isEqualToString:@"Cartoon"]) {
                [videoFx setBooleanVal:@"Stroke Only" val:info.strokeOnly];
                [videoFx setBooleanVal:@"Grayscale" val:info.grayscale];
            }
        } else {
            NvsTimelineVideoFx *videoFx = [timeline addPackagedTimelineVideoFx:inPoint duration:duration videoFxPackageId:info.name];
            [videoFx setFilterIntensity:info.strength];
        }
    }
}

+ (void)rebuildTimelineStructure:(NvsVideoTrack *)videoTrack
                      audioTrack:(NvsAudioTrack *)audioTrack
                  timelineFxMode:(int)timelineFxMode
              timelineFxPosition:(int64_t)timelineFxPosition
{
    if (videoTrack == nil || audioTrack == nil) {
        return;
    }
    [videoTrack removeAllClips];
    [audioTrack removeAllClips];
    
    NvTimelineData *timelineData = [NvTimelineData sharedInstance];
    if (timelineFxMode == TIMELINE_FX_REVERSE) {
        for (int i = (int)timelineData.editDataArray.count - 1; i >= 0; i--) {
            NvEditDataModel *editDataModel = timelineData.editDataArray[i];
            NvsVideoClip *videoClip = [videoTrack appendClip:editDataModel.videoPath
                                                      trimIn:editDataModel.trimIn
                                                     trimOut:editDataModel.trimOut];
            if (!videoClip) {
                continue;
            }
            [videoClip changeSpeed:editDataModel.speed keepAudioPitch:YES];
            [videoClip setPlayInReverse:YES];
            [videoClip setVolumeGain:0 rightVolumeGain:0];
        }
    } else {
        for (int i = 0; i < timelineData.editDataArray.count; i++) {
            NvEditDataModel *editDataModel = timelineData.editDataArray[i];
            NvsVideoClip *videoClip = [videoTrack appendClip:editDataModel.videoPath
                                                      trimIn:editDataModel.trimIn
                                                     trimOut:editDataModel.trimOut];
            if (!videoClip) {
                continue;
            }
            [videoClip changeSpeed:editDataModel.speed keepAudioPitch:YES];
            [videoClip setVolumeGain:0 rightVolumeGain:0];
        }
    }
    
    for (int i = 0; i < timelineData.editDataArray.count; i++) {
        NvEditDataModel *editDataModel = timelineData.editDataArray[i];
        NvsAudioClip *audioClip = [audioTrack appendClip:editDataModel.videoPath
                                                  trimIn:editDataModel.trimIn
                                                 trimOut:editDataModel.trimOut];
        [audioClip changeSpeed:editDataModel.speed keepAudioPitch:YES];
        if (editDataModel.mute) {
            [audioClip setVolumeGain:0 rightVolumeGain:0];
        } else {
            [audioClip setVolumeGain:editDataModel.volume rightVolumeGain:editDataModel.volume];
        }
        
    }
    
    if (timelineFxMode == TIMELINE_FX_REPEAT) {
        [NvTimelineUtils doRepeatTimeline:timelineFxPosition videotrack:videoTrack originCutTrimInfo:nil];
    } else if (timelineFxMode == TIMELINE_FX_SLOWMOTION) {
        [NvTimelineUtils doSlowMotionTimeline:timelineFxPosition videotrack:videoTrack];
    }
    
    [NvTimelineUtils removeAllTransitions:videoTrack audioTrack:audioTrack];
}

+ (BOOL)doRepeatTimeline:(uint64_t)point videotrack:(NvsVideoTrack*_Nullable)track originCutTrimInfo:(NvRecordingInfo *)info
{
    if(!track) {
        return NO;
    }
    
    uint64_t duration = track.duration;
    uint64_t repeatDuration = NV_TIME_BASE / 2;
    if(duration < repeatDuration) {
        return NO;
    }
    
    if(duration < (point + repeatDuration)) {
        point = duration - repeatDuration;
    }
    
    if(![NvTimelineUtils splitClip:point videotrack:track]) {
        NSLog(@"splitClip失败");
        return NO;
    }
    
    if(![NvTimelineUtils splitClip:point+repeatDuration videotrack:track]) {
        NSLog(@"splitClip--point+repeatDuration--失败");
        return NO;
    }
    //splite clip
    NSMutableArray* cliplist = [NvTimelineUtils getClipRange:point duration:repeatDuration videotrack:track];
    if(cliplist.count < 1) {
        NSLog(@"cliplist.count < 1");
        return NO;
    }
    
    NSMutableArray* durationArray = [[NSMutableArray alloc] init];
    uint64_t segmentDuration = repeatDuration;
    for(int i = 0; i < cliplist.count; i++){
        NvsVideoClip* orgClip = cliplist[i];
        uint64_t newTrimOut = orgClip.trimIn + segmentDuration;
        if(newTrimOut > orgClip.trimOut)
            newTrimOut = orgClip.trimOut;
        
        NSNumber *clipDuration = [NSNumber numberWithLongLong:newTrimOut - orgClip.trimIn];
        segmentDuration = segmentDuration - (newTrimOut - orgClip.trimIn);
        [durationArray addObject:clipDuration];
    }
    
    NvsVideoClip* lastClip = cliplist[cliplist.count - 1];
    int clipIndex = lastClip.index + 1;
    if(clipIndex >= track.clipCount)
        clipIndex = track.clipCount - 1;
    for(int i = 0; i < 4; i++){
        
        //insert clip
        bool bReverse = false;
        int start = 0;
        int end = (int)cliplist.count - 1;
        int step = 1;
        if((i % 2) == 0){
            bReverse = true;
            start = (int)cliplist.count - 1;
            end = 0;
            step = -1;
        }
        
        for(int n = start; bReverse ? n >= end : n <= end; n+= step) {
            NvsVideoClip* orgClip = cliplist[n];
            uint64_t duration_new = [durationArray[n] longLongValue];
            NSString *orgFilePath = orgClip.filePath;
            int64_t trimIn_new = orgClip.trimIn;
            int64_t trimOut = duration_new + orgClip.trimIn;
            if(bReverse){
                NSString *lastPath = [orgFilePath lastPathComponent];
                orgFilePath = [CONVERTPATH stringByAppendingPathComponent:lastPath];
                int64_t dur = [[[NvSDKUtils getSDKContext] getAVFileInfo:orgFilePath] getVideoStreamDuration:0];
                
                if (info.asset) {
                    orgFilePath = info.convertPath;
                    dur = info.trimOut-info.trimIn;
                }
                
                trimIn_new = dur - duration_new - (orgClip.trimIn - info.trimIn);
                if (trimIn_new < 0) {
                    trimIn_new = 0;
                }
                trimOut = dur - (orgClip.trimIn - info.trimIn);
                
            }
            NvsVideoClip* clip = [track insertClip:orgFilePath trimIn:trimIn_new trimOut:trimOut clipIndex:clipIndex];
            if(!clip){
                continue;
            }
            
            float lvolumeGain = 1.0f;
            float rvolumeGain = 1.0f;
            double speed = [orgClip getSpeed];
            NvsExtraVideoRotation rotation = [orgClip getExtraVideoRotation];
            [orgClip getVolumeGain:&lvolumeGain rightVolumeGain:&rvolumeGain];
            
//            [clip setImageMotionROI:&startROI endROI:&endROI];
            if (speed > 1) {
                [clip changeSpeed:speed keepAudioPitch:YES];
            } else {
                [clip changeSpeed:speed*1.5 keepAudioPitch:YES];
            }
            
            [clip setVolumeGain:0.0f rightVolumeGain:0.0f];
            [clip setExtraVideoRotation:rotation];
//            if(bReverse)
//                [clip setPlayInReverse:YES];
            
            clipIndex++;
        }
        
    }
    
    //超出时长移除
    [track removeRange:duration endTimelinePos:track.duration keepSpace:false];
    
//    [NvTimelineUtils MuteVideoTrack:track];
    return YES;
}

+ (BOOL)doSlowMotionTimeline:(uint64_t)point videotrack:(NvsVideoTrack*_Nullable)track
{
    if(!track)
        return NO;
    uint64_t duration = track.duration;
    if(duration < NV_TIME_BASE)
        return NO;
    
    if(duration < (point + NV_TIME_BASE))
        point = duration - NV_TIME_BASE;
    
    if(![NvTimelineUtils splitClip:point videotrack:track])
        return NO;
    
    if(![NvTimelineUtils splitClip:point + NV_TIME_BASE videotrack:track])
        return NO;
    
    uint64_t prePoint = 0;
    uint64_t preduration = NV_TIME_BASE;
    uint64_t afterPoint = point + NV_TIME_BASE;
    uint64_t afterduration = NV_TIME_BASE;
    if(point <  NV_TIME_BASE){
        preduration = point;
        afterduration += NV_TIME_BASE - preduration;
        if((afterPoint + afterduration) > duration)
            afterduration = duration - afterPoint;
    }else{
        if((afterPoint + afterduration) > duration)
            afterduration = duration - afterPoint;
        
        preduration += (NV_TIME_BASE - afterduration);
        if(preduration > point){
            preduration = point;
        }
        prePoint = point - preduration;
    }
    
    if(point > preduration){
        //need split clips
        [NvTimelineUtils splitClip:prePoint videotrack:track];
    }
    
    if((afterPoint + afterduration) < duration){
        //need split clips
        [NvTimelineUtils splitClip:afterPoint + afterduration videotrack:track];
    }
    //splite clip
    NSMutableArray* cliplist = [NvTimelineUtils getClipRange:point duration:NV_TIME_BASE videotrack:track];
    if(cliplist.count < 1)
        return NO;
    
    //pre clip
    
    NSMutableArray* precliplist = [NvTimelineUtils getClipRange:prePoint duration:preduration videotrack:track];
    
    //after process
    
    NSMutableArray* aftercliplist = [NvTimelineUtils getClipRange:afterPoint duration:afterduration videotrack:track];
    
    //
    for(int i = 0; i < cliplist.count; i++){
        NvsVideoClip* orgClip = cliplist[i];
        
        double speed = [orgClip getSpeed];
        [orgClip changeSpeed:speed / 2.0 keepAudioPitch:YES];
    }
    //慢动作前面的时间点快放
    for(int i = 0; i < precliplist.count; i++){
        NvsVideoClip* orgClip = precliplist[i];

        double speed = [orgClip getSpeed];
        [orgClip changeSpeed:speed * 2.0 keepAudioPitch:YES];
    }
    //慢动作后面的时间点快放
    for(int i = 0; i < aftercliplist.count; i++){
        NvsVideoClip* orgClip = aftercliplist[i];

        double speed = [orgClip getSpeed];
        [orgClip changeSpeed:speed * 2.0 keepAudioPitch:YES];
    }
    //超出时长移除
    if(duration < track.duration)
        [track removeRange:duration endTimelinePos:track.duration keepSpace:false];
    //mute all clips
//    [NvTimelineUtils MuteVideoTrack:track];
    return YES;
}

+ (NSMutableArray*) getClipRange:(uint64_t)point duration:(uint64_t)duration videotrack:(NvsVideoTrack*_Nullable)track
{
    NSMutableArray* cliplist = [[NSMutableArray alloc] init];
    
    uint64_t ptindex = point;
    while(ptindex < (point + duration)){
        NvsVideoClip* clip = [track getClipWithTimelinePosition: ptindex];
        if(!clip)
            break;
        
        ptindex = clip.outPoint;
        [cliplist addObject:clip];
    }
    
    return cliplist;
}

+(BOOL)splitClip:(uint64_t)point videotrack:(NvsVideoTrack*_Nullable)track
{
    if(!track)
        return NO;
    
    NvsVideoClip* splitClip = [track getClipWithTimelinePosition:point];
    if(splitClip.inPoint != point){
        if (![track splitClip:splitClip.index splitPoint:point]) {
            return NO;
        }
    }
    
    return YES;
}
+ (BOOL)MuteVideoTrack:(NvsVideoTrack*_Nullable)track
{
    if(!track)
        return NO;
    
    for(int i = 0; i < track.clipCount; i++){
        NvsVideoClip* orgClip = [track getClipWithIndex:i];
        [orgClip setVolumeGain:0 rightVolumeGain:0];
        [orgClip setPan:0 andScan:1];
        
    }
    
    return YES;
}

+ (void)removeAllVideoTransitions:(NvsVideoTrack *)videoTrack {
    if (videoTrack == nil)
        return;
    for (int i = 0; i < (int)videoTrack.clipCount - 1; i++) {
        [videoTrack setBuiltinTransition:i withName:nil];
    }
}

+ (void)removeAllTransitions:(NvsVideoTrack *)videoTrack audioTrack:(NvsAudioTrack *)audioTrack {
    if (videoTrack == nil)
        return;
    for (int i = 0; i < (int)videoTrack.clipCount - 1; i++) {
        [videoTrack setBuiltinTransition:i withName:nil];
    }
    if (audioTrack == nil)
        return;
    for (int i = 0; i < (int)audioTrack.clipCount - 1; i++) {
        [audioTrack setBuiltinTransition:i withName:nil];
    }
}

+ (int64_t)getClipInpoint:(NvsTimeline *)timeline clipInfo:(NvEditDataModel *)clipInfo {
    NSMutableArray *editDataArray = [[NvTimelineData sharedInstance] editDataArray];
    for (int i = 0; i < editDataArray.count; i++) {
        NvEditDataModel *info = editDataArray[i];
        if ([info.uuid isEqualToString:clipInfo.uuid]) {
            NvsVideoTrack *videoTrack = [timeline getVideoTrackByIndex:0];
            NvsVideoClip *clip = [videoTrack getClipWithIndex:i];
            return clip.inPoint;
        }
    }
    return 0;
}

+ (int64_t)getClipOutpoint:(NvsTimeline *)timeline clipInfo:(NvEditDataModel *)clipInfo {
    NSMutableArray *editDataArray = [[NvTimelineData sharedInstance] editDataArray];
    for (int i = 0; i < editDataArray.count; i++) {
        NvEditDataModel *info = editDataArray[i];
        if ([info.uuid isEqualToString:clipInfo.uuid]) {
            NvsVideoTrack *videoTrack = [timeline getVideoTrackByIndex:0];
            NvsVideoClip *clip = [videoTrack getClipWithIndex:i];
            return clip.outPoint;
        }
    }
    return 0;
}

+ (NvsVideoClip *)getTimelineVideoClip:(NvsTimeline *)timeline clipInfo:(NvEditDataModel *)clipInfo {
    NSMutableArray *editDataArray = [[NvTimelineData sharedInstance] editDataArray];
    for (int i = 0; i < editDataArray.count; i++) {
        NvEditDataModel *info = editDataArray[i];
        if ([info.uuid isEqualToString:clipInfo.uuid]) {
            NvsVideoTrack *videoTrack = [timeline getVideoTrackByIndex:0];
            return [videoTrack getClipWithIndex:i];
        }
    }
    return nil;
}

+ (NSMutableArray<NvsThumbnailSequenceDesc *> *)getThumbnailSequenceDescArray:(NvsTimeline *)timeline {
    NvsVideoTrack *videoTrack = [timeline getVideoTrackByIndex:0];
    NSMutableArray *thumbnailSequenceDescArray = [NSMutableArray array];
    for (int i = 0; i < videoTrack.clipCount; i++) {
        NvsVideoClip *videoClip = [videoTrack getClipWithIndex:i];
        NvsThumbnailSequenceDesc *info = [[NvsThumbnailSequenceDesc alloc] init];
        info.stillImageHint = NO;
        info.mediaFilePath = videoClip.filePath;
        info.trimIn = videoClip.trimIn;
        info.trimOut = videoClip.trimOut;
        info.inPoint = videoClip.inPoint;
        info.outPoint = videoClip.outPoint;
        
        [thumbnailSequenceDescArray addObject:info];
    }
    return thumbnailSequenceDescArray;
}

+ (void)seekTimeline:(NvsTimeline *)timeline atTime:(int64_t)atTime {
    NvsStreamingContext *context = [NvsStreamingContext sharedInstance];
    [context seekTimeline:timeline timestamp:atTime videoSizeMode:NvsVideoPreviewSizeModeLiveWindowSize flags:0];
}

+ (void)playTimeline:(NvsTimeline *)timeline atTime:(int64_t)atTime {
    NvsStreamingContext *context = [NvsStreamingContext sharedInstance];
    [context playbackTimeline:timeline startTime:atTime endTime:-1 videoSizeMode:NvsVideoPreviewSizeModeLiveWindowSize preload:YES flags:0];
}

@end
